如果你使用了Spring容器(无论是ApplicationContext或是BeanFactory)来管理的你业务对象——你应该这么做——你会希望使用Spring`s AOP FactoryBeans。(Factory Bean引入了中间层,使它可以创建任何类型的对象)
Spring AOP支持也使用了factory beans。
在Spring中创建AOP代理的基本方式是通过org,springframework.aop.framework.ProxyFactoryBean
。它可以完全的控制要应用的切点和通知,以及他们的顺序。然而,如果你不需要这些控制的话还可以选择更简单的方式。
和其他Spring的FactoryBean
的实现一样,ProxyFactoryBean
引入了中间层。如果你将ProxyFactoryBean
名字定义为foo
,那么foo
所引用的对象其实是ProxyFactoryBean
的getObject所创建的对象,而并非是ProxyFactoryBean
的实例本身。getObject方法创建了AOP代理,代理包装了目标对象。
用ProxyFactoryBean
或是其他知道IoC的类去创建AOP代理的好吃是通知和切点同样可以被IoC管理。这是个强大个功能,使得一些其他AOP框架难以实现的功能都可以实现。比如,一个通知可能自身引用了一个应用程序对象(除了目标外,它对任何AOP框架都是可用的),将会从注入带来的可插拔性中获益。
和大部分Spring提供的FacotryBean
实现一样,ProxyFactoryBean
类本身也是一个JavaBean。他的属性被用来:
指出你要代理的目标
指出是否使用CGLIB(查看下文和12.5.3节,"JDK- and CGLIB-based proxies")。
一些关键的属性从org.springframework.aop.framework.ProxyConfig
(Spring中其他AOP代理工厂的父类)继承。这些关键的属性包括:
proxyTargetClass
:true
表示代理的是类,而不是接口。如果这个属性被这只为true
,那么将会创建CGLIB代理(同见12.5.3节,"JDK- and CGLIB-based proxies")。
optimize
:控制是否将积极的优化应用到CGLIB创建的代理上。除非你对AOP代理如何处理优化的相关方面非常了解,否则不要使用这个设置。它只对CGLIB代理有效,而对JDK 动态代理无效。
frozen
:如果一个代理的配置是forzen
,那么不再允许改这个配置。在轻微的优化,或是你不希望调用者在代理被创建之后(通过Advised
接口)操作代理,那么它是有用的。默认值为false
,因此像添加其他通知这类的修改是被允许的。
exposeProxy
:决定代理是否被暴露在ThreadLocal
中,让目标可以访问它。如果exposeProxy
属性被设为true
,那么在目标想要获得代理时,可以使用AopContext.currentProxy()
方法。
ProxyFactoryBean
特有的一些属性包括:
proxyInterfaces
:接口名称的数组。如果没有提供,那么对会对目标的类使用CGLIB代理(同见12.5.3节,"JDK- and CGLIB-based proxies")。
interceptorNames
:对应用的Advisor
,拦截器或是其他通知名字的字符串数组。顺序是有意义的。这意味着列表中的第一个拦截器会第一个对调用进行拦截。
name是当前和父工厂中存在的bean的名字。你不能引用bean,因为让ProxyFactoryBean
无法保证通知是单例的。
你可以在拦截器的名字后加一个星号(*
)。这会让程序中所有名字以星号前部分打头的advisor bean都被应用。这个特性的例子可以在12.5.6节,"Using 'global' advisors"中找到。
* singleton:工厂是否返回单例的对象,不管调用了getObject()
方法多少次。许多FactoryBean
的实现提供了这个方法。默认值是true
。如果你希望使用有状态的通知——比如,有状态的混合——设置singleton的值为false
来使用原型的通知。
本节作为关于ProxyFactoryBean如何为特定目标对象(即将被代理)选择创建基于JDK或CGLIB的代理的权威性文档。
ProxyFactoryBean
创建基于JDK或是CGLIB代理的行为在Spring1.2.x和2.0中发生了变化。现在,ProxyFactoryBean
和TransactionProxyFactoryBean
类的自动检测接口的机制差不多。
如果被代理的目标对象的class(以下简称为目标类)没有实现任何接口,那么将会创建基于CGLIB的代理。这很容易理解,因为JDK代理是基于接口的,没有接口就意味着无法使用JDK代理。可以简单的插入目标bean,并通过interceptorNames
属性指定拦截器列表。注意,即使ProxyFactory
的proxyTargetClass
属性被设置为false
,基于CGLIB的代理仍会被创建。(很明显这时proxyTargetClass
是无效的,最好从bean的定义中移除,因为它是多余的,并且会让人迷惑。)
如果目标类实现了一个(或多个)接口,那创建的代理的类型取决于ProxyFactoryBean
的配置。
如果ProxyFactoryBean
的proxyTargetClass
属性被设为true
,那么将会创建基于CGLIB的代理。这样做是有道理的,并且符合最小惊喜原则。即使ProxyFactoryBean
的proxyInterfaces
的属性设置成了一个或多个全限定的接口名称,由于proxyTargetClass
属性被设置成了true
,最后还是会创建基于CGLIB的代理。
如果ProxyFactoryBean
的proxyInterfaces
属性被设置成了一个或多个接口的全限定名称,那么会创建基于JDK的代理。所创建的代理实现了proxyInterfaces
属性中所指出的接口;如果代理目标的接口要比proxyInterfaces
属性指定的接口多,那么多余的接口并不会被被返回的代理所实现。
如果ProxyFactroyBean
的proxyInterfaces
属性没有被设置,但是目标类确实实现了一个(或多个)接口,那么ProxyFactoryBean
会自动检测到目标类至少有一个接口,并且会创建基于JDK的代理。目标类所有的接口都会被代理所实现;事实上,这个简单的将每一个接口都写在proxyInterfaces
属性上是一样的。但是,这显然免去了很多工作,减少了出错的可能。
让我们看一个ProxyFactoryBean
的实际例子。这个例子涉及:
一个代理的目标bean。就是下面例子中的"personTarget"。
用来负责通知的Advisor和Interceptor。
* 一个AOP代理的bean定义,指定了目标对象(personTarget bean)和代理的接口,以及要应用的通知。
<bean id="personTarget" class="com.mycompany.PersonImpl">
<property name="name" value="Tony"/>
<property name="age" value="51"/>
</bean>
<bean id="myAdvisor" class="com.mycompany.MyAdvisor">
<property name="someProperty" value="Custom string property value"/>
</bean>
<bean id="debugInterceptor" class="org.springframework.aop.interceptor.DebugInterceptor">
</bean>
<bean id="person"
class="org.springframework.aop.framework.ProxyFactoryBean">
<property name="proxyInterfaces" value="com.mycompany.Person"/>
<property name="target" ref="personTarget"/>
<property name="interceptorNames">
<list>
<value>myAdvisor</value>
<value>debugInterceptor</value>
</list>
</property>
</bean>
注意,interceptorNames
属性接受字符串数组:在当前工厂中的interceptor或是advisor的名称。可以使用Advisors,interceptors, before, after returning 和 throws advice对象。advisors的顺序是有意义的。
你可能会好奇为什么List不直接保存bean的引用。这是因为如果ProxyFactoryBean的singleton属性被设置为false,它必须返回独立的代理实例。如果advisor自身就是原型的,那么也需要返回独立的实例,因此能够从工厂中获得原型是必要的;而直接保存引用做不到这一点。
上面定义的"Person"可以用来代表一个Person的实现:
Person person = (Person) factory.getBean("person");
它和其他普通的Java对象一样,可以被同个IoC上下文中的其他bean依赖:
<bean id="personUser" class="com.mycompany.PersonUser">
<property name="person"><ref bean="person"/></property>
</bean>
这个例子中的PersonUser
暴露了一个Person类型的属性。对这个例子而言,AOP代理可以透明的代替“真实的”person实现。但它的class是动态代理的class。可以被转成Advised
接口(之后讨论)。
可以像下面这样使用使用匿名内部的bean来隐藏目标和代理的区别。只有ProxyFactoryBean
定义和之前的不同;
<bean id="myAdvisor" class="com.mycompany.MyAdvisor">
<property name="someProperty" value="Custom string property value"/>
</bean>
<bean id="debugInterceptor" class="org.springframework.aop.interceptor.DebugInterceptor"/>
<bean id="person" class="org.springframework.aop.framework.ProxyFactoryBean">
<property name="proxyInterfaces" value="com.mycompany.Person"/>
<!-- Use inner bean, not local reference to target -->
<property name="target">
<bean class="com.mycompany.PersonImpl">
<property name="name" value="Tony"/>
<property name="age" value="51"/>
</bean>
</property>
<property name="interceptorNames">
<list>
<value>myAdvisor</value>
<value>debugInterceptor</value>
</list>
</property>
</bean>
这样做的好处是只有一个Person
类型的对象:这在防止应用程序上下文的用户获取到未被通知的对象和在避免任何使用Spring IoC自动装配时产生的歧义上,是有帮助的。这样做可能还有其他好处。但是,有时候能够从工厂获取到未被通知的目标对象也有好处:比如,在某些测试环境中。
当你需要代理一个类,而不是一个或多个接口时要怎么做呢?
假设上面的例子中,没有Person
接口:我们需要通知一个叫的Person
类,它并没有实现任何接口。这种情况下,你可以配置Spring来使用基于CGLIB的代理,而不是动态代理。只需将将上面的ProxyFactoryBean的proxyTargetClass
属性设为true。尽管,最好是针对接口进行编程,而不是针对类,但是对于处理遗留代码是,能够针对未实现接口的类通知的功能是很有用的。(这是通常的做法,并非Spring规定的。应用这个好的方法其实很简单,而且避免了强制使用某些特殊的方法。)
如果你希望的话,你可以在任何情况下,甚至有接口的情况下,强制使用CGLIB。
CGLIB在运行时为代理类生成子类。生成的子类将内部的方法委托给原目标:子类利用了装饰器模式,织入了通知。
CGLIB代理通常对用户来说是透明的,但是,还有一些问题需要考虑:
Final
方法不能够被通知,因为他们不能被重写。
不需要将CGLIB添加到你的类路径下。从Spring 3.2期,CGLIB被打包进spring-core包里。换句换说,基于CGLIB的AOP可以像JDK动态代理一样直接拿来使用。
CGLIB代理和动态代理仍有些不同。从Spring 1.0起,动态代理会稍微快一些。但是,这点在将来可能会有所改变。性能不应该成为决定性的考虑因素。
通过给一个拦截器的名字后面添加幸好,所有的名字以星号前部分打头的advisors都会被添加到通知链中。如果你需要添加一组标准的“全局”advisor,这会很有用:
<bean id="proxy" class="org.springframework.aop.framework.ProxyFactoryBean">
<property name="target" ref="service"/>
<property name="interceptorNames">
<list>
<value>global*</value>
</list>
</property>
</bean>
<bean id="global_debug" class="org.springframework.aop.interceptor.DebugInterceptor"/>
<bean id="global_performance" class="org.springframework.aop.interceptor.PerformanceMonitorInterceptor"/>